/**
 * @author Rid King
 * @since 2018-03-01
 * @version 2.0.0
 * @description CMD规范模块承载器
 */
;(function () {
	'use strict'

	// 工具
	var utils = {
		/**
		 * @description 检测v的类型
		 * @param {Any} v 被检测的变量
		 * @return {String}
		 */
		_type : function(v){
			return Object.prototype.toString.call(v);
		},

		/**
		 * @description 是否为字符串类型 如果是就返回true 如果不是就返回false
		 * @param {Any} v 被检测的变量
		 * @return {Boolean}
		 */
		isString : function(v){
			return typeof v === 'string';
		},

		/**
		 * @description 是否为数字类型(为Number且不为正负无穷大数字) 如果是就返回true 如果不是就返回false
		 * @param {Any} v 被检测的变量
		 * @return {Boolean}
		 */
		isNumber : function(v){
			return typeof v === 'number' && isFinite(v);
		},

		/**
		 * @description 是否为布尔值类型  如果是就返回true 如果不是就返回false
		 * @param {Any} v 被检测的变量
		 * @return {Boolean}
		 */
		isBoolean : function(v){
			return typeof v === 'boolean';
		},

		/**
		 * @description 是否为正则表达式类型  如果是就返回true 如果不是就返回false
		 * @param {Any} v 被检测的变量
		 * @return {Boolean}
		 */
		isRegexp : function(v){
			return this._type(v) === '[object RegExp]';
		},

		/**
		 * @description 是否为函数类型 如果是就返回true 如果不是就返回false
		 * @param {Any} v 被检测的变量
		 * @return {Boolean}
		 */
		isFunction : function(v){
			return this._type(v) === '[object Function]';
		},

		/**
		 * @description 是否为数组对象类型  如果是就返回true 如果不是就返回false
		 * @method isArray
		 * @param {Any} v 被检测的变量
		 * @return {Boolean} 结果
		 */
		isArray : function(v){
			return this._type(v) === '[object Array]';
		},

		/**
		 * @description 是否为对象类型
		 * @param {Any} v 被检测的变量
		 * @return {boolean}
		 */
		isObject : function(v){
			return !!v && typeof v === 'object';
		},

		/**
		 * @description 是否为绝对对象类型
		 * @param {Any} v 被检测的变量
		 * @return {boolean}
		 */
		isPlainObject : function(v){
			return !!v && this._type(v) === '[object Object]';
		},

		/**
		 * @description 是否为DOM元素
		 * @param {Any} v 被检测的变量
		 * @return {Boolean}
		 */
		isDOM : function(v){
			var func = ( typeof HTMLElement === 'object' ) ?
				function(obj){
					return obj instanceof HTMLElement;
				} :
				function(obj){
					return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
				}

			return func(v);
		},

		/**
		 * @description 是否为DOM元素节点
		 * @param {Any} v 被检测的变量
		 * @return {Boolean}
		 */
		isElementNode : function(node){
			return node && node.nodeType === 1;
			/*
			 节点类型	nodeType属性值
			 元素节点	1
			 属性节点	2
			 文本节点	3
			 */
		},

		/**
		 * @description 加载js
		 * @param url <String> js地址
		 * @param [callback <Function> 回调函数]
		 */
		loadScript: function (url, callback) {
			var node = document.createElement('script')
			node.setAttribute('defer', true)
			node.setAttribute('async', true)

			// 加载事件
			if('onload' in node){
				node.onload = callback
				node.onerror = function() {
					callback(false)
				}
			}else{
				node.onreadystatechange = function () {
					if(/loaded|complete/i.test(node.readyState) || node.complete){
						callback()
					}
				}
			}

			node.setAttribute('src', url)
			head.appendChild(node)
		},

		/**
		 * @description 加载css
		 * @param url <String> css地址
		 * @param [callback <Function> 回调函数]
		 */
		loadStyle: function (url, callback) {
			var node = document.createElement('link')
			node.setAttribute('rel', 'stylesheet')

			// 加载事件
			if('onload' in node){
				node.onload = callback
				node.onerror = function() {
					callback(false)
				}
			}else{
				node.onreadystatechange = function () {
					if(/loaded|complete/i.test(node.readyState) || node.complete){
						callback()
					}
				}
			}

			node.setAttribute('href', url)
			head.appendChild(node)
		},

		/**
		 * @description 遍历对象
		 * @param obj <String/Array/Object> 需要遍历的对象
		 * @param func <Function> 遍历处理节点方法
		 */
		forEach : function (obj, func) {
			if (utils.isArray(obj)) {
				for (var i = 0; i < obj.length; i++) {
					var b = func(obj[i], i, obj)
					if (b === false) {
						break
					}
				}
			} else if (typeof obj === 'string') {
				var n = 0
				for (var item in obj) {
					var b = func(obj[item], n, obj)
					n += 1
					if (b === false) {
						break
					}
				}
			} else if (typeof obj === 'object' && obj !== null) {
				for (var property in obj) {
					if (obj.hasOwnProperty(property)) {
						var b = func(obj[property], property, obj)
						if (b === false) {
							break
						}
					}
				}
			}
		},

		/**
		 * @description 继承
		 * @param {Boolean} b 是否深复制
		 * @return {Object} arg1 复制的对象1
		 * @return {Object} arg2 复制的对象2
		 */
		extend : function(b, arg1, arg2){
			var options, name, src, copy, copyIsArray, clone,
				target = arguments[0] || {},
				i = 1,
				length = arguments.length,
				deep = false;

			if ( typeof target === "boolean" ) {
				deep = target;
				target = arguments[1] || {};
				i = 2;
			}

			if ( typeof target !== "object" && !this.isFunction(target) ) {
				target = {};
			}

			if ( length === i ) {
				target = this;
				--i;
			}

			for ( ; i < length; i++ ) {
				options = arguments[ i ];
				if ( options !== null && this.isObject(options) && !this.isDOM(options) && !this.isElementNode(options)) {
					for ( name in options ) {
						src = target[ name ];
						copy = options[ name ];
						if ( target === copy ) {
							continue;
						}
						if ( deep && copy && ( this.isPlainObject(copy) || (copyIsArray = this.isArray(copy)) ) ) {
							if ( copyIsArray ) {
								copyIsArray = false;
								clone = src && this.isArray(src) ? src : [];
							} else {
								clone = src && this.isPlainObject(src) ? src : {};
							}
							target[ name ] = this.extend( deep, clone, copy );
						} else if ( copy !== undefined ) {
							target[ name ] = copy;
						}
					}
				}
			}
			return target;
		}
	}

	/**
	 * @desc 模块类
	 * @param options <Object> 配置
	 * {
	 * 	path: <String> // 标识
	 * 	factory: <Function> // 模块构造体
	 * }
	 * */
	function Module(options){
		this.status = _STATUS_.NONE // 初始状态
		this.exports = {} // 导出体

		// 标识是字符串的情况下默认为标识
		options = utils.isObject(options) ? options : 
		utils.isString(options) ? {path: options} : {}
		this.path = options.path
		this.deps = options.deps
		this.factory = options.factory

		// 生成模块即队列+1，完成构造体解析则-1
		_PIG_.queue += 1

		// 加载构造体
		this.load()
	}

	// 原型链
	Module.prototype = {
		/**
		 * @desc 编译模块构造体
		*/
		compile: function () {
			// 已编译完成
			if (this.status === _STATUS_.COMPILED) {
				return this.exports
			}

			var exports = this.factory()
			// 无值情况默认空对象
			if (exports === undefined) {
				exports = this.exports
			}
			this.exports = exports
			// 状态改为编译完成
			this.status = _STATUS_.COMPILED
			// 删除函数构造体
			delete this.factory

			return this.exports
		},

		/**
		 * @desc 模块加载成功并初始化完成后回调
		*/
		launch: function () {
			var callback = _PIG_.calls[0]
			// 检测是否可执行回调
			if (_PIG_.queue < 1 && callback) {
				_PIG_.calls.shift()
				callback()
				this.launch()
			}
		},

		/**
		 * 推进依赖加载
		*/
		urge: function (paths) {
			// 加载依赖模块
			utils.forEach(paths, function (path) {
				Module.getModule(path)
			})
		}
	}

	/**
	 * @desc js模块
	*/
	function ScriptModule () {
		Module.apply(this, arguments)
	}
	ScriptModule.prototype = utils.extend({}, Module.prototype, {
		/**
		 * @desc 加载模块脚本
		*/
		load: function () {
			// 设置状态为加载中
			this.status = _STATUS_.LOADING

			// 本地函数体构造
			if (this.factory) {
				this.buildFactory()
			}
			// 远程函数体构造
			else {
				var me = this
				utils.loadScript(Module.parsePath(this.path), function () {
					me.buildFactory()
				})
			}
		},

		// 解析与构造模块体
		buildFactory: function () {
			var factory = this.factory || _PIG_.module.factory
			this.alias = this.alias || _PIG_.module.alias
			this.deps = this.deps || []
			// 依赖模块
			var deps = this.deps
			deps = deps.concat(_PIG_.module.deps || [])
					.concat(Module.getDeps([this.path, this.alias]))
			// 使用后置空，避免被非模块写法的脚本影响
			_PIG_.module.factory = undefined
			_PIG_.module.alias = undefined
			_PIG_.module.deps = undefined
			delete this.deps

			// 封闭导出模块构造体
			this.factory = function () {
				// 进入编译状态
				this.status = _STATUS_.COMPILING
				// 判断构造体是否是函数，否则直接返回
				return utils.isFunction(factory) ?
				factory(Module.require, this.exports, this) : factory
			}

			// 加载、构造完成
			this.status = _STATUS_.LOADED

			// 解析内部require引用
			var paths = this.parseRequire(factory)
			deps = deps.concat(paths)

			// 下载依赖
			this.urge(deps)

			// 完成结构体后队列-1
			_PIG_.queue -= 1

			// 执行回调
			this.launch()
		},

		/**
		 * 解析require引用
		*/
		parseRequire: function (factory) {
			// 默认无依赖
			var paths = []

			// 非构造函数体不解析
			if (!utils.isFunction(factory)) {
				return paths
			}

			// 把构造体转化为字符串，查找require引用的模块标识
			var facStr = factory.toString()

			/*
			* require引用分3种形式：1.require('/path');2.require(['/path']);3.
			* */
			var reg = new RegExp(/require\s*\(\s*[\'\"]([^\'\"]+)[\'\"]/, 'g'), path
			while (path = reg.exec(facStr)) {
				paths.push(path[1])
			}

			return paths
		}
	})
	ScriptModule.prototype.constructor = ScriptModule

	/**
	 * @desc css模块
	*/
	function StyleModule () {
		Module.apply(this, arguments)
	}
	StyleModule.prototype = utils.extend({}, Module.prototype, {
		/**
		 * @desc 加载模块脚本
		*/
		load: function () {
			// 设置状态为加载中
			this.status = _STATUS_.LOADING

			// 下载样式
			var me = this, cssPath = Module.parsePath(this.path)
			cssPath = cssPath.replace(_CONFIG_.cssSign, '')
			utils.loadStyle(cssPath, function () {
				me.buildFactory()
			})
		},

		// 解析与构造模块体
		buildFactory: function () {
			this.deps = this.deps || []
			// 依赖模块
			var deps = this.deps
			deps = deps.concat(Module.getDeps([this.path, this.alias]))
			// 使用后置空，避免被非模块写法的脚本影响
			delete this.deps

			// 封闭导出模块构造体
			this.factory = function () {
				// 进入编译状态
				this.status = _STATUS_.COMPILING
				// 返回空对象
				return {}
			}

			// 加载、构造完成
			this.status = _STATUS_.LOADED

			// 下载依赖
			this.urge(deps)

			// 完成结构体后队列-1
			_PIG_.queue -= 1

			// 执行回调
			this.launch()
		}
	})
	StyleModule.prototype.constructor = StyleModule

	/**
	 * 模块定义
	 * @param alias <String> // 模块标识别名
	 * @param deps <Array> // 模块依赖数组
	 * @param factory <Function> // 模块的构造方法，factory 为对象、字符串时，表示模块的接口就是该对象、字符串。
	 * */
	Module.define = function (alias, deps, factory) {
		if (utils.isFunction(deps)) {
			factory = deps
			if (utils.isArray(alias)) {
				deps = alias
				alias = undefined
			} else {
				alias = String(alias)
				deps = []
			}
		} else if (utils.isFunction(alias)) {
			factory = alias
			deps = []
			alias = undefined
		}

		// 将值赋给当前导出模块构造体
		Module.setFactory(factory, alias, deps)
	}

	// cmd规范
	Module.define.cmd = {
		pig: true
	}

	/**
	 * 模块引用
	 * @param path <String> // 模块标识
	 * */
	Module.require = function (path) {
		var mod = Module.getModule(path)

		return mod.compile()
	}

	/**
	 * @desc 加载执行异步模块
	 * @param paths <Array/String> 依赖模块标识
	 * @param callback <Function> 回调函数
	*/
	Module.request = function (paths, callback) {
		// 无依赖
		if (utils.isFunction(paths)) {
			callback = paths
			paths = []
		}
		// 字符串形式
		else if (utils.isString(paths)) {
			paths = [paths]
		}

		paths = utils.isArray(paths) ? paths : []
		callback = utils.isFunction(callback) ? callback : function () {}
		// 加载依赖模块
		var mods = []
		utils.forEach(paths, function (item) {
			mods.push(Module.getModule(item))
		})

		return Module.requestPig(paths, callback, mods)
	}
	// 模块内部require提供异步加载功能
	Module.require.request = Module.request

	/**
	 * @desc 获取模块管理者
	 * @param paths <Array> 依赖标识数组
	 * @param callback <Function> 回调函数
	 * @param mods <Array<Module>> 模块参数
	 * @param pig <Pig> 模块管理者
	 */
	Module.requestPig = function (paths, callback, mods, pig) {
		// 入口模块创建，检测内在依赖并加载
		var mod = new ScriptModule({
			path: 'native_' + String(Math.random()).replace('.', ''), // 本地模块
			factory: callback, // 传入空构造体
			deps: paths
		})

		// 保存依赖模块项，返回新的管理者
		if (mods instanceof Pig) {
			pig = mods
		} else {
			pig = new Pig({
				mods: mods
			})
		}

		// 添加异步回调
		Module.callPlus(pig, callback)

		return pig
	}

	/**
	 * @desc 解析地址,根据标识或具体地址,参照root返回网络地址
	 * @return <String>
	 * */
	Module.parsePath = function (path) {
		var root = _CONFIG_.root, parsePath
		root = root.replace(/\/+$/, '')

		// 遍历标识列表，存在则取出
		utils.forEach(_CONFIG_.paths, function (item, key) {
			if (key === path) {
				parsePath = item
				return false
			}
		})
		// 标识列表中不存在则按传入地址处理
		if (!parsePath) {
			parsePath = path
		}

		// 处理非网络地址或非根目录地址
		if (!/^\/|^http(s)?\s*\:/.test(parsePath)) {
			parsePath = parsePath.replace(root + '/', '')
			// 组合生成网络资源地址
			parsePath = root + '/' + parsePath
		}

		// 替换/./为空，为非转换的地址添加尾部.js后缀
		parsePath = parsePath.replace(/\/\.\//g, '/')
		// 替换/../为上级地址

		// css模块判断
		var suffix = 'js'
		if (parsePath.indexOf(_CONFIG_.cssSign) != -1) {
			suffix = 'css'
		}

		// 转换地址，无需后缀的
		if (/^keep\!/.test(parsePath)) {
			parsePath = parsePath.replace('keep!', '')
		}
		// 非转换地址
		else {
			parsePath = parsePath.replace(RegExp('\\.' + suffix + '$'), '') + '.' + suffix
		}

		return parsePath
	}

	/**
	 * @desc 根据path获取模块
	 * @param path <String> // 模块标识
	 * @return <Module>
	*/
	Module.getModule = function (path) {
		// 非字符串不执行
		if (!utils.isString(path)) {
			throw new TypeError('Path should be string')
		} 

		var mod, parsePath = Module.parsePath(path)

		// 检测标识别名表中是否存在
		utils.forEach(_CONFIG_.paths, function (item, key) {
			if (key === path) {
				parsePath = Module.parsePath(item)
				return false
			}
		})

		// 检测模块表中是否存在已实例化的模块
		utils.forEach(_PIG_.modules, function (item) {
			if (Module.parsePath(item.path) === parsePath || 
				item.alias === path) {
				mod = item
			}
		})

		// 不存在模块
		if (!mod) {
			var ClassModule = ScriptModule

			// css模块判断
			if (parsePath.indexOf(_CONFIG_.cssSign) != -1) {
				ClassModule = StyleModule
			}

			mod = new ClassModule({
				path: path
			})
			// 加入列表
			_PIG_.modules.push(mod)
		}

		return mod
	}

	/**
	 * @desc 设置模块构造体
	 * @param factory <any> // 内容
	 * @param path <String> // 模块标识
	 * @param deps <Array> // 模块依赖数组
	*/
	Module.setFactory = function (factory, path, deps) {
		_PIG_.module.factory = factory
		_PIG_.module.alias = path
		if (utils.isArray(deps) && deps.length > 0) {
			_PIG_.module.deps = deps
		}
	}

	/**
	 * @desc 获取模块的依赖模块列表
	 * @param paths <Array> // 模块标识
	*/
	Module.getDeps = function (paths) {
		var deps = []
		if (utils.isArray(paths) && paths.length > 0) {
			var existPaths = []
			// 检测标识别名表中是否存在键值对应的情况
			utils.forEach(paths, function (node, n) {
				utils.forEach(_CONFIG_.paths, function (item, key) {
					if (node === item) {
						existPaths.push(key)
					}
				})
			})
			paths = paths.concat(existPaths)

			// 检测依赖表
			utils.forEach(_CONFIG_.shim, function (item, key) {
				utils.forEach(paths, function (node, n) {
					if (key === node) {
						deps = deps.concat(item.deps || [])
					}
				})
			})
		}

		return deps
	}

	/**
	 * @desc 添加回调
	*/
	Module.callPlus = function (pig, callback) {
		// 非函数不添加到列表中
		if (!utils.isFunction(callback)) {
			return false
		}
		_PIG_.calls.push(function () {
			var exps = []
			// 指定依赖模块的情况
			utils.forEach(pig._mods, function (item) {
				exps.push(item.compile())
			})
			// 加入require
			exps.push(Module.require)

			callback.apply(this, exps)
		})
	}

	// 模块常量
	var _PIG_ = {
		// 当前模块
		module: {
			exports: undefined, // 导出体
			factory: undefined, // 构造函数体
			deps: undefined, // 依赖数组
			alias: undefined // 别名
		},
		/**
		 * 模块列表
		 * 模块的path为相对于root的地址，避免更新root后地址不同步
		 * */ 
		modules: [
			// Module1, Module2
		],
		/**
		 * 队列长度
		 *  */
		queue: 0,
		/**
		 * 回调队列
		*/
		calls: []
	}

	// 配置常量
	var _CONFIG_ = {
		/**
		 * 模块标识配置
		 * 标识键名为别名，键值为相对于root的脚本地址，可不带.js后缀
		 * 例：paths: {
		 * 	'user': './js/user',
		 * 	'book': './js/book.js'
		 * }
		 * */
		paths: {},
		/**
		 * 模块依赖
		 * 例：shim: {
		 * 	'user': {
		 *		'deps' : ['book']
		 *	}
		 * }
		*/
		shim : {
			'user' : {
				'deps' : ['book']
			}
		},
		/**
		 * 根路径,默认为当前地址
		*/
		root: '',
		/**
		 * css标记名
		*/
		cssSign: 'css!',
		/**
		 * 是否开启debug信息
		*/
		debug: false
	}

	// 模块状态表
	var _STATUS_ = {
		UNLOAD: 1, // 未加载
		LOADING: 2, // 加载中
		LOADED: 3, // 加载完成
		READY: 4, // 依赖模块已加载完成，准备完成
		COMPILING: 5, // 编译中
		COMPILED: 6 // 编译完成
	}

	// 全局dom变量
	var doc = document
	var head = doc.head || doc.getElementsByTagName('head')[0] || doc.documentElement // head element

	/**
	 * 模块管理者
	 * @param options <Object> 配置项
	 * {
	 * 	mods: <Array> 依赖模块数组
	 * }
	*/
	function Pig (options) {
		options = utils.isPlainObject(options) ? options : {}
		this._mods = []

		// 依赖模块添加
		if (utils.isArray(options.mods)) {
			this._mods = options.mods
		}
	}
	Pig.prototype = {
		/**
		 * @desc 执行模块
		 * @param paths <Array/String> 依赖模块标识
		 * @param callback <Function> 回调函数
		*/
		request: Module.request,

		/**
		 * 执行回调
		*/
		then: function (callback) {
			if (utils.isFunction(callback)) {
				Module.requestPig([], callback, this)
			}

			// 返回原始管理者
			return this
		},

		/**
		 * @desc 配置
		 * @param settings <Object> 配置对象
		*/
		config: function (settings) {
			settings = utils.isPlainObject(settings) ? settings : {}
			var optoins = {}
			for (var i in settings) {
				if (!_CONFIG_.hasOwnProperty(i)) {
					continue
				}
				optoins[i] = settings[i]
			}
			utils.extend(true, _CONFIG_, optoins)
		}
	}

	/**** 初始化操作 ****/
	var bestPig = new Pig
	// 根路径配置
	var locate = window.location.href
	locate = ((locate.split('#'))[0]).split('?')[0] // 去除参数与hash
	locate = locate.slice(0, locate.lastIndexOf('/')) // 提升地址为上级地址
	bestPig.config({
		root: locate
	})

	// 对象与方法输出
	window.pig = bestPig
	window.define = Module.define
})();